package org.truenewx.tnxjee.repo.redis.prepare;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.boot.ApplicationContextRunner;
import org.truenewx.tnxjee.core.util.ClassUtil;
import org.truenewx.tnxjee.core.util.LogUtil;
import org.truenewx.tnxjee.core.util.SpringUtil;
import org.truenewx.tnxjee.core.util.function.ProfileSupplier;
import org.truenewx.tnxjee.core.version.Version;

@Component
public class RedisDataPrepareCommandRunner implements ApplicationContextRunner {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedisDataPrepareCommandParser parser;
    @Autowired
    private ProfileSupplier profileSupplier;
    private String cacheKey = "tnxjee:repo:redis:prepare:version";

    public RedisDataPrepareCommandRunner(Environment environment) {
        String appName = SpringUtil.getApplicationName(environment);
        if (StringUtils.isNotBlank(appName)) {
            this.cacheKey += Strings.COLON + appName;
        }
    }

    @Override
    public int getOrder() {
        return ApplicationContextRunner.DEFAULT_ORDER - 10; // 比默认顺序早
    }

    @Override
    public void run(ApplicationContext context) throws Exception {
        Resource[] resources = ClassUtil.getClasspathResources("redis/*.xml");
        if (resources.length > 0) {
            List<VersionResource> vrs = new ArrayList<>();
            for (Resource resource : resources) {
                vrs.add(new VersionResource(resource));
            }
            Collections.sort(vrs);
            ValueOperations<String, String> operations = this.redisTemplate.opsForValue();
            String preparedVersionValue = operations.get(this.cacheKey);
            Version preparedVersion = StringUtils.isBlank(preparedVersionValue) ? null : new Version(
                    preparedVersionValue);
            for (VersionResource vr : vrs) {
                Version version = vr.getVersion();
                if (preparedVersion == null || preparedVersion.compareTo(version) < 0) {
                    List<RedisDataPrepareCommand> commands = this.parser.parse(vr.getResource());
                    for (RedisDataPrepareCommand command : commands) {
                        execute(command);
                    }
                    preparedVersion = version;
                    operations.set(this.cacheKey, preparedVersion.toString());
                }
            }
        }
    }

    private void execute(RedisDataPrepareCommand command) {
        String[] profiles = command.getProfiles();
        if (ArrayUtils.isEmpty(profiles) || ArrayUtils.contains(profiles, this.profileSupplier.get())) {
            if (command instanceof RedisDataDeleteCommand) {
                RedisDataDeleteCommand deleteCommand = (RedisDataDeleteCommand) command;
                List<String> includes = deleteCommand.getIncludes();
                List<String> excludes = deleteCommand.getExcludes();
                for (String include : includes) {
                    Set<String> keys = this.redisTemplate.keys(include);
                    if (keys != null) {
                        for (String key : keys) {
                            if (excludes == null || !excludes.contains(key)) {
                                this.redisTemplate.delete(key);
                                LogUtil.info(getClass(), "==== Redis data '{}' deleted.", key);
                            }
                        }
                    }
                }
            }
        }
    }

    private static class VersionResource implements Comparable<VersionResource> {

        private Resource resource;
        private Version version;

        public VersionResource(Resource resource) {
            this.resource = resource;
            this.version = getVersion(resource);
        }

        public Resource getResource() {
            return this.resource;
        }

        public Version getVersion() {
            return this.version;
        }

        private static Version getVersion(Resource resource) {
            String filename = resource.getFilename();
            if (filename != null) {
                String version = filename.substring(0, filename.lastIndexOf(Strings.DOT));
                return new Version(version);
            }
            return new Version("0.0.0");
        }

        @Override
        public int compareTo(VersionResource other) {
            return this.version.compareTo(other.version);
        }
    }

}
